﻿#include <string.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include "wechat.h"
#include "win.h"

int current_video_index = 0;
struct video_info video_info[MAX_VIDEO_NUM];

static DWORD mitm_id = 0;
HANDLE hThread_read = NULL; //read thread

static char line_content[LINE_MAX_LEN];
static char line_words[LINE_MAX_LEN];

char* fgets_pipe(char* buf, int buf_len, HANDLE pipe);

/*
first run:
1. install certificate
certificate not installed, installed it;

2. delete cache
io.appData("..\Roaming\Tencent\WeChat\radium\web\profiles")
e.g. C:\Users\xxx\AppData\Roaming\Tencent\WeChat\radium\web\profiles


3. pass the firewall by system setting

step 1-3, only do once.

4. set http proxy
*/
void mitm_init(void)
{
	gstate = APP_STATE_UNINIT;
	set_http_proxy();
	kill_mitm();
	gstate = APP_STATE_INITED;
}

//reset http proxy
void mitm_exit(void) 
{
	gstate = APP_STATE_EXITING;
	reset_http_proxy();
	stop_mitm(mitm_id, hThread_read);
	gstate = APP_STATE_EXIT;
}

DWORD WINAPI read_thread(LPVOID lpParam)
{
	printf("read thread is running \n");

	struct video_info* video = &video_info[current_video_index];
	int got = 0;
	got = parse_http_packet(hReadPipe, video);

	CloseHandle(hReadPipe);
	return 0;
}

/*
* main thread, main process
	.\mitmdump.exe -s dump_json.py --flow-detail 2

	TCHAR szCmdline[] = TEXT("cmd.exe /c dir");
*/
int mitm_start(void)
{
	//1. start mitmdump service
	TCHAR szCmdline[] = TEXT("cmd.exe /c .\\extra\\mitmdump.exe -s .\\extra\\dump_json.py --flow-detail 2");
	wprintf(TEXT("%s\n"), szCmdline);

	int ret = start_mitm(szCmdline);
	if (ret) {
		printf("failed to start mitm monitor\n");
		return -1;
	}
	Sleep(2000); //wait mitm service start

	hThread_read = NULL;
	mitm_id = GetProcessIdByName(L"mitmdump.exe");
	if (!mitm_id) {
		stop_mitm(mitm_id, hThread_read);
		return -1;
	}
	printf("mitm process PID is %d \n", mitm_id);

	//only for debugging
#ifdef DEBUG
	//sleep 10 seconds, then kill process
	wechat_catpture();
	Sleep(2000);	
#else
	//2. create read thread to read output of mitmdump
	DWORD threadId;
	hThread_read = CreateThread(NULL, 0, read_thread, NULL, 0, &threadId);
#endif

	gstate = APP_STATE_RUN;
	return 0;
}



int is_stop_char(char c)
{
	switch (c) {
	case ' ':
	case '	':
	case '\r':
	case '\n':
		return 1;
	}
	return 0;
}

//return start pointer of next word
char* get_word(char* buf, char* word)
{
	char* head = buf;
	char* cur = head;
	char c;

	int len = 0;
	while (*head) {
		while (is_stop_char(*head) && *head) {
			head++;
			continue;
		}
		if (!(*head))
			break;
		cur = head;

		if (*head == '[') {
			cur = head;
			while (*head != ']' && *head)
				head++;
			head++;
			c = *head;
			*head = 0;
			len = (int)(head - cur);
			if (len >= LINE_MAX_LEN) {
				goto too_long_buf;
			}
			strcpy(word, cur);
			*head = c;
			return head;
		}

		while (!is_stop_char(*head) && *head) {
			head++;
			continue;
		}
		if (!(*head))
			break;
		len = (int)(head - cur);
		if (len >= LINE_MAX_LEN) {
			goto too_long_buf;
		}
		c = *head;
		*head = 0;
		strcpy(word, cur);
		*head = c;
		return head;
	}

too_long_buf:
	if (len >= LINE_MAX_LEN) {
		printf("error, get_word len is more than 4096 bytes\n");
		len = LINE_MAX_LEN - 1;
		memcpy(word, cur, len);
		word[len] = 0;
		return head;
	}

	return NULL;
}


//return 1 if meet CRLF
//return 0 otherwise
int goto_line_end(char* buf)
{
	char* head = buf;
	while (*head) {
		if (*head == '\r' || *head == '\n') {
			head++;
			return 1;
		}
		head++;
	}
	return 0;
}


/*
* 127.0.0.1:15892: GET 
* ...
* [09:00:43.229][127.0.0.1:15892] client disconnect
*/
void goto_next_mark(FILE* fp, enum next_state symbol)
{
	int ret;
	enum log_mark mark = MARK_INIT;

	while (1) {
		if (fgets_common(line_content, sizeof(line_content), fp) == NULL) {
			//reach tail
			break;
		}
		
		if (symbol == NEXT_MARK_ALL) {
			mark = parse_one_line(line_content);
			if (mark == MARK_INIT)
				continue;
			//meet some mark
			break;
		} else { //NEXT_MAKR_LINEEND
			ret = goto_line_end(line_content);
			if (ret) //find CRLF
				return;
		}
	}

}

void printf_short_url(char* buf)
{
	char* head = buf;

	head = get_word(head, line_words);
	printf("%s ", line_words);

	head = get_word(head, line_words);
	printf("%s ", line_words);

	//https://wx.qlogo.cn/mmhead
	char* tail;
	tail = head + strlen("https://") + 1;
	tail = strchr(tail, '/');
	if (!tail)
		return;
	*tail = 0;
	printf("%s\n", head);
	*tail = '/';
}

/*
{
  "args": {},
  "data": "..."
  "form": {},
  "headers": {
	"Accept-Encoding": "gzip, deflate, br",
	"Accept-Language" : "zh-CN,zh;q=0.9",
	"Content-Length" : "1134",
	"Content-Type" : "application/json",
	"Host" : "www.httpbin.org",
	"Origin" : "https://channels.weixin.qq.com",
	"Priority" : "u=1, i",
	"Referer" : "https://channels.weixin.qq.com/",
	"Sec-Fetch-Dest" : "empty",
	"Sec-Fetch-Mode" : "cors",
	"Sec-Fetch-Site" : "cross-site",
	"X-Amzn-Trace-Id" : "Root=1-6897ef43-33dedcca770f3dd55eaf55db"
  },
  "json": {
		"createtime": 1754709642,
			"duration" : 15066,
			"fileFormat" : [
				"xWT111",
					"xWT112",
					"xWT156",
					"xWT157",
					"xWT113",
					"xWT158"
			] ,
			"id": "14719571344689859092",
					"key" : "944906605",
					"nickname" : "...",
					"nonce_id" : "15326867668207159310_0_141_0_0",
					"size" : 3058675,

find "json":

{
  "errCode": 0,
  "errMsg": "request successful",
  "data": {}
}
*/
int goto_json_body(FILE* fp)
{
	int ret = 1;
	char* head;

	while (1) {
		if (fgets_common(line_content, sizeof(line_content), fp) == NULL) {
			//reach tail
			break;
		}

		head = get_word(line_content, line_words);
		if (!memcmp(line_words, "\"json\":", strlen("\"json\":"))) {
			return 0;
		}
		if (!memcmp(line_words, "\"errCode\":", strlen("\"errCode\":"))) {
			return 1;
		}
	}
	return ret;
}

/*
 "title": "xx", this is default

 "title": "\u5929\u5b50\u6e5680\u65b9\u5c0f\u4e24\u5ba42\u697c\u597d\u697c\u5c42\u91c7\u5149\u597d\uff0c\u5bb6\u7535\u9f50\u5168\u62ce\u5305\u5165\u4f4f\u3002",
 
 "title": "#\u5b89\u5409\u7cbe\u88c5\u4fee#"
 
 video title: xxxx\n#xxx
 \u7528\u4eba\u6c11\u5e01\u611f\u53d7\u65e5\u672c\u5927\u962a\u623f\u4ef7\n#\u65e5\u672c\u623f\u4e
 */
static char utf_title[LINE_MAX_LEN];

/*
	src : \u5929
	dst :

	\uff0c: comma

return:
	0: add this to title
	1: ignore it
*/
char comma_stop[6] = {'\\', 'u', 'f', 'f', '0', 'c'};
int handle_utf16_chars(char* src, char* dst)
{
	char temp[5];
	unsigned short decimal_num;
	char* cur = src;
	int high;

	cur += 2; //skip \u
	//59 29
	memcpy(temp, cur, 4);
	cur += 4;
	temp[4] = 0;
	sscanf(temp, "%hx", &decimal_num);
	high = (decimal_num & 0xff00) >> 8;
	if (high >= 0xd8)
		return 1;
	memcpy(dst, &decimal_num, 2);
	return 0;
}

/*
#title: title, ignore the first #
title#: title, stop at #

\n
\uff0c: comma ，
*/
void handle_json_title(char* buf, char* title)
{
	char* head = buf;
	char* cur;
	char temp[2];

	memset(utf_title, 0, sizeof(utf_title));
	cur = head + 1;
	if (*cur == 0) {
		strcpy(utf_title, (const char*)(L"未知标题"));
		goto convert_utf;
	}

	//cur point to head of title
	int i = 0;
	char* ptitle = utf_title;
	temp[1] = 0;
	if (*cur == '#')
		cur++;
	int ignore = 0;
	while (*cur) {
		if (!memcmp(cur, comma_stop, 6)) {
			break;
		}
		if (!memcmp(cur, "\\u", 2)) {
			//utf16, e.g. \u5929, handle 6 bytes
			ignore = handle_utf16_chars(cur, ptitle);
			cur += 6;
			i += 6;
			if(!ignore)
				ptitle += 2;
			continue;
		}

		if (*cur == '\"') //end of title
			break;
		if (*cur == '#')
			break; //stop symbol
		if (*cur == '\\' && *(cur + 1) == 'n')
			break; //end of title\n
		//Ascii code, handle one byte, 1 ascii to 2 bytes
		temp[0] = *cur;
		memcpy(ptitle, temp, 2);
		ptitle += 2;
		cur++;
		i++;
	}

	printf("ori title is: ");
	head++; //skip "
	while (head != cur) {
		printf("%c", *head);
		head++;
	}
	printf("\n");
	if (utf_title[0] == 0) {
		//NULL title
		strcpy(title, "no title");
		return;
	}

	convert_utf:
	int len;
	cur = utf16_to_gbk(utf_title, &len);
	if (!cur)
		return;
	len >>= 1;

	char* tail;
	if (len >= MAX_SHOW_TITLE_LEN) {
		//cut the title
		tail = cur + MAX_SHOW_TITLE_LEN;
		*tail = 0;
	}
	strcpy(title, cur);
	free(cur);
}

//according video title, check if video repeat
//return 1 if repeat
int check_repeat_video(struct video_info* videos, struct video_info* cur)
{
	int total = current_video_index;
	int i;
	for (i = 0; i < total; i++) {
		if (!strcmp(cur->title, videos[i].title))
			return 1;
	}
	return 0;
}

/*
"json": {
	"createtime": 1754709642,
	"duration": 15066,
	"fileFormat": [
	  "xWT111",
	  "xWT112",
	  "xWT156",
	  "xWT157",
	  "xWT113",
	  "xWT158"
	],
	"id": "14719571344689859092",
	"key": "944906605",
	"nickname": "xx",
	"nonce_id": "15326867668207159310_0_141_0_0",
	"size": 3058675,
	"title": "xx",
	"url": xxx
  },

  "duration": 15066, 15066ms
  "key": "944906605", seed
  "size": 3058675, Bytes
  "title": "xx", GBK is default

  "title": "\u623f\u4e1c\u957f\u5f97\u6709\u70b9\u50cf\u4e00\u4f4d\u6b4c\u624b\u00b7\u00b7\u00b7#Vlog",
*/
void handle_json_data(FILE* fp, struct video_info* video)
{
	char* head;
	char* cur;
	char* tail;

	static struct video_info current_video;
	struct video_info* pvideo = &current_video;
	
	if (goto_json_body(fp))
		return;

	int url_flag = 0;
	printf("start to parse JSON data\n");
	while (1) {
		if (fgets_common(line_content, sizeof(line_content), fp) == NULL) {
			//reach tail
			break;
		}

		//analysis one line
		head = line_content;
		while (head) {
			head = get_word(head, line_words);
			if (!head)
				break;
			if (!strcmp(line_words, "\"duration\":")) {
				head = get_word(head, line_words);
				pvideo->duration = atoi(line_words) / 1000;
				break;
			}

			if (!strcmp(line_words, "\"fileFormat\":")) {
				while (fgets_common(line_content, sizeof(line_content), fp)) {
					if (strchr(line_content, ']'))
						break;
				}
				break;
			}

			if (!strcmp(line_words, "\"key\":")) {
				get_word(head, line_words);
				//"944906605",
				cur = line_words + 1;
				tail = line_words + strlen(line_words) - 2;
				*tail = 0;
				pvideo->seed = atol(cur);
				break;
			}

			if (!strcmp(line_words, "\"size\":")) {
				get_word(head, line_words);
				pvideo->size = atol(line_words); //bytes
				pvideo->size = pvideo->size >> 20; //MB
				break;
			}

			if (!strcmp(line_words, "\"title\":")) {
				//"title": "\u5929\u5b50
				get_word(head, line_words);
				handle_json_title(line_words, pvideo->title);
				break;
			}

			if (!strcmp(line_words, "\"url\":")) {
				get_word(head, line_words);
				tail = strchr(line_words + 1, '\"');
				if(tail)
					*tail = 0;
				strcpy(pvideo->url, line_words + 1);
				url_flag = 1;
				if (check_repeat_video(video_info, pvideo)) {
					printf("repeat video: %s\n", pvideo->title);
					return;
				}
				memcpy(video, pvideo, sizeof(struct video_info));
				current_video_index++;
				if (current_video_index > MAX_VIDEO_NUM) {
					printf("video number is more than %d\n", MAX_VIDEO_NUM);
				}
				printf("video num is %d\n", current_video_index);

				printf("video duration: %d seconds \n", video->duration);
				printf("video seed: %llu \n", video->seed);
				printf("video size: %llu MB\n", video->size);
				printf("video title: %s \n", video->title);
				printf("%s\n", video->url);
				break;
			}

			//default
			break;
		}

		if (url_flag) //reach tail
			break;
	}

	printf("end to parse JSON data\n");
}

enum log_mark parse_one_line(char* line)
{
	enum log_mark mark = MARK_INIT;
	char* head = line;

	head = get_word(head, line_words);

	//[] ...
	if (line_words[0] == '[') {
		mark = MARK_SB_INFO; //[] ...
		head = get_word(head, line_words);
		if (line_words[0] != '[')
			return MARK_SB_INFO;
		mark = MARK_SB2_INFO; //[] [] ...
		if (!head)
			return MARK_SB2_INFO;

		head = get_word(head, line_words);
		if (!strcmp(line_words, "Unexpected"))
			return MARK_SB_UNEXPECT;

		return mark;
	}

	//127.0.0.1:15892: GET/POST/HEAD/OPTIONS
	if (!memcmp(line_words, "127.0.0.1:", strlen("127.0.0.1:"))) {
		//local IP
		head = get_word(head, line_words);
		if (!strcmp(line_words, "GET")) {
			mark = MARK_LIP_GET;
		} else if (!strcmp(line_words, "POST")) {
			mark = MARK_LIP_POST;
		} else if (!strcmp(line_words, "OPTIONS")) {
			mark = MARK_LIP_OPTIONS;
		} else if (!strcmp(line_words, "HEAD")) {
			mark = MARK_LIP_HEAD;
		}

		return mark;
	}

	//json?
	if (line_words[0] == '{') {
		mark = MARK_JSON;
		return mark;
	}
	return mark;
}

//127.0.0.1:15948: HEAD https://finder.video.qq.com/251/20302/stodownload?encfilekey=oi
void handle_http_head(char* buf, FILE* fp, struct video_info* video)
{
	printf("http HEAD\n");

	char* head = buf;
	head = get_word(head, line_words); //skip 127.0.0.1:15948:
	head = get_word(head, line_words); //skip  HEAD
	head = get_word(head, line_words);
	//len = strlen(line_words);

	char* tail = strstr(line_words, "X-snsvideoflag");
	//&taskid=pc-1754787650292688887 &fexam=1 &X-snsvideoflag=xWT156
	if (tail) {
		//remove &X-snsvideoflag=xWT156
		tail--;
		*tail = 0;
	}
	strcpy(video->url, line_words);
	printf("%s\n", line_words);
}

/*
* read one line from pipe
* return NULL if read fail: pipe closed
* return buf address if not
*/
char* fgets_pipe(char* buf, int buf_len, HANDLE pipe)
{
	DWORD bytesRead = 1;
	BOOL ret = FALSE;
	char temp[16];
	int len = 0;
	memset(buf, 0, buf_len);

	while (1) {
		//read one byte from pipe
		ret = ReadFile(pipe, temp, 1, &bytesRead, NULL);
		if (!ret || !bytesRead)
			break;
		buf[len] = temp[0];
		len++;
		if (len >= (buf_len - 1)) {
			//buf[buf_len - 1] = 0;
			return buf;
		}
		if (temp[0] == '\r' || temp[0] == '\n')
			return buf;
	}
	return NULL;
}

/*
* https://finder.video.qq.com/251/20302/stodownload?encfilekey=oibeqy
*/
int parse_http_packet(FILE* fp, struct video_info* pvideo)
{
	enum log_mark mark = MARK_INIT;
	struct video_info* video = pvideo;

	while (1) {
		//read one line
		if (fgets_common(line_content, sizeof(line_content), fp) == NULL) {
			//reach tail
			break;
		}

		if (current_video_index >= MAX_VIDEO_NUM) {
			continue;
		}
		video = &pvideo[current_video_index];

	new_mark:
		mark = parse_one_line(line_content);
		switch (mark) {
		case MARK_SB_INFO:
		case MARK_SB2_INFO:
			break;
		case MARK_SB_UNEXPECT:
			goto_next_mark(fp, NEXT_MAKR_LINEEND);
			goto new_mark;
			break;
		case MARK_LIP_GET:
		case MARK_LIP_POST:
		case MARK_LIP_OPTIONS:
			//printf_short_url(line_content);
			goto_next_mark(fp, NEXT_MARK_ALL);
			goto new_mark;
			break;
		case MARK_LIP_HEAD:
			//handle_http_head(line_content, fp, video);
			goto_next_mark(fp, NEXT_MARK_ALL);
			goto new_mark;
			break;
		case MARK_JSON:
			handle_json_data(fp, video);
			goto_next_mark(fp, NEXT_MARK_ALL);
			goto new_mark;
			break;
		default:
			break;
		} //end switch
	} //end while

	return 0;
}


int wechat_catpture(void)
{
#ifdef DEBUG
	FILE* fp_mitm = NULL;

	fp_mitm = fopen("..\\resources\\log.txt", "r");

	if (!fp_mitm)
		return -1;

	int got = 0;
	struct video_info* video = video_info;
	got = parse_http_packet(fp_mitm, video);

	//download_video(video);
	//decode_video_with_seed(video->seed, "enc.mp4", "dec.mp4");

	fclose(fp_mitm);
#endif
	return 0;
}
